//
//  TMLoader.m
//
//
//  Created by 周良飞 on 16/11/23.
//  Copyright (c) 2016年 e360. All rights reserved.
//
#import "TMLoader.h"
#import <SystemConfiguration/SystemConfiguration.h>
#import <CommonCrypto/CommonCrypto.h>
#import <objc/runtime.h>

//
#pragma mark-
#pragma mark TMLoader
@interface TMLoader () <NSStreamDelegate, NSURLSessionDataDelegate>{
    dispatch_semaphore_t semaphore;
    unsigned long long bytesLoaded;
    unsigned long long bytesTotal;
}
@property(nonatomic, copy) NSString *localPath;
@property(nonatomic, copy) NSString *cachePath;
@property(nonatomic, strong) NSURLSession *session;
@property(nonatomic, strong) NSOutputStream *outStream;
@property(nonatomic, strong) NSURLSessionDataTask *dataTask;
@property(nonatomic, weak) id<TMLoaderDelegate> delegate;
@property(nonatomic, assign) BOOL cache;
@end
@implementation TMLoader
-(instancetype)initWithDelegate:(id<TMLoaderDelegate>)delegate cache:(BOOL)cache{
    self = [super init];
    if (self) {
        [self setDelegate:delegate];
        [self setCache:cache];
        semaphore = nil;
    }
    return self;
}
-(void)load:(NSURLRequest*)request file:(NSString*)file{
    [self close];
    if (request) {
        if(file){
            [self setFile:file];
        }else{
            [self setFile:[request.URL.absoluteString lastPathComponent]];
        }
        //
        NSFileManager *manager = [NSFileManager defaultManager];
        UIDeviceNetwork network = [[UIDevice currentDevice] network];
        //
        [self setLocalPath:[self fileAppend:self.file]];
        if ([manager fileExistsAtPath:self.localPath] && (network == UIDeviceNetworkNone || self.cache)) {
            [self onComplete:nil];
        }else{
            if (network != UIDeviceNetworkNone) {
                [self setRequest:request];
                [self setCachePath:[self cacheAppend:self.file]];
                if ([manager fileExistsAtPath:self.cachePath]) {
                    if ([self.request respondsToSelector:@selector(addValue:forHTTPHeaderField:)]) {
                        bytesLoaded = [[manager attributesOfItemAtPath:self.cachePath error:nil] fileSize];
                        [(id)self.request addValue:[NSString stringWithFormat:@"bytes=%llu-", bytesLoaded] forHTTPHeaderField:@"Range"];
                    }else{
                        [manager removeItemAtPath:self.cachePath error:nil];
                    }
                }
                //
                __weak typeof(self) this = self;
                NSBlockOperation *opera = [NSBlockOperation blockOperationWithBlock:^{
                    NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
                    [this setSession:[NSURLSession sessionWithConfiguration:config delegate:this delegateQueue:[NSOperationQueue mainQueue]]];
                    [this setDataTask:[this.session dataTaskWithRequest:this.request]];
                    [this.dataTask resume];
                    [this waiting];
                }];
                //
                NSOperationQueue *queue = [[NSOperationQueue alloc] init];
                [queue addOperation:opera];
                [opera waitUntilFinished];
            }else{
                NSError *error = [[NSError alloc]  initWithDomain:NSCocoaErrorDomain code:400 userInfo:nil];
                [self onComplete:error];
            }
        }
    }else{
        NSError *error = [[NSError alloc]  initWithDomain:NSCocoaErrorDomain code:0 userInfo:nil];
        [self onComplete:error];
    }
}
-(void)waiting{
    if (self.delegate == nil && semaphore == nil) {
        semaphore = dispatch_semaphore_create(0);
        if (semaphore) {
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        }else{
            semaphore = nil;
        }
    }
}
-(void)close{
    bytesTotal = 0;
    bytesLoaded = 0;
    
    if (semaphore) {
        dispatch_semaphore_signal(semaphore);
    }
    semaphore = nil;

    if(self.dataTask){
        [self.dataTask cancel];
        [self setDataTask:nil];
    }
    
    if(self.session){
        [self.session invalidateAndCancel];
        [self setSession:nil];
    }
    
    if (self.outStream) {
        [self.outStream close];
        [self setOutStream:nil];
    }
}
-(NSString *)filePath{
    return [self.localPath copy];
}
-(NSData*)data{
    if (self.localPath) {
        NSFileManager *manager = [NSFileManager defaultManager];
        if ([manager fileExistsAtPath:self.localPath]) {
            return [NSData dataWithContentsOfFile:self.localPath];
        }
    }
    return nil;
}
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSHTTPURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler{
    completionHandler(NSURLSessionResponseAllow);
    bytesTotal = [response.allHeaderFields[@"Content-Length"] integerValue] + bytesLoaded;
    //
    [self setOutStream:[NSOutputStream outputStreamToFileAtPath:self.cachePath append:YES]];
    [self.outStream open];
    [self onOpen];
}
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{
    [self.outStream write:data.bytes maxLength:data.length];
    //
    bytesLoaded += data.length;
    [self onProgress];
}
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
    NSFileManager *manager = [NSFileManager defaultManager];
    if (error == nil) {
        if ([manager fileExistsAtPath:self.cachePath]) {
            NSString *directory = [self.localPath stringByDeletingLastPathComponent];
            if ([manager fileExistsAtPath:directory] == NO) {
                [manager createDirectoryAtPath:directory withIntermediateDirectories:YES attributes:nil error:nil];
            }else{
                if ([manager fileExistsAtPath:self.localPath]) {
                    [manager removeItemAtPath:self.localPath error:nil];
                }
            }
            [manager moveItemAtPath:self.cachePath toPath:self.localPath error:&error];
        }
    }
    [self onComplete:error];
    [self close];
}
-(void)onOpen{
    if (self.delegate && [self.delegate respondsToSelector:@selector(openLoader:)]) {
        __weak typeof(self) this = self;
        [self execute:[NSOperationQueue mainQueue] block:^{
            [this.delegate openLoader:this];
        }];
    }
}
-(void)onComplete:(NSError*)error{
    if (self.delegate && [self.delegate respondsToSelector:@selector(completeLoader:error:)]) {
        __weak typeof(self) this = self;
        [self execute:[NSOperationQueue mainQueue] block:^{
            [this.delegate completeLoader:this error:error];
        }];
    }
}
-(void)onProgress{
    if (self.delegate && [self.delegate respondsToSelector:@selector(progressLoader:bytesLoaded:bytesTotal:)]) {
        __weak typeof(self) this = self;
        [self execute:[NSOperationQueue mainQueue] block:^{
            [this.delegate progressLoader:this bytesLoaded:bytesLoaded bytesTotal:bytesTotal];
        }];
    }
}
-(NSString*)fileAppend:(NSString*)path{
    NSString *root = [NSHomeDirectory() stringByAppendingPathComponent:@"Library"];
    if ([path isKindOfClass:NSString.class] && [path length] > 0) {
        return [root stringByAppendingPathComponent:path];
    }
    return root;
}
-(NSString*)cacheAppend:(NSString*)path{
    NSString *root = [NSHomeDirectory() stringByAppendingPathComponent:@"Library/Caches"];
    if ([path isKindOfClass:NSString.class]) {
        NSString *md5 = [path md5];
        NSString *ext = [path pathExtension];
        if (ext && [ext length] > 0) {
            md5 = [md5 stringByAppendingPathExtension:ext];
        }
        return [root stringByAppendingPathComponent:md5];
    }
    return root;
}
-(void)execute:(NSOperationQueue*)queue block:(void (^)(void))block{
    if ([NSOperationQueue currentQueue] != queue) {
        NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:block];
        [queue addOperation:operation];
        [operation waitUntilFinished];
    }else{
        block();
    }
}
@end


//
#pragma mark-
#pragma mark NSURLRequest
@implementation NSURLRequest (TMLoader)
+(NSURLRequest*)requestWithURL:(NSURL*)url post:(id)post{
    if (url) {
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
        if (post) {
            if ([post isKindOfClass:[NSDictionary class]]) {
                NSString *boundary = @"----64F4845541AEA084";
                NSMutableData *body = [NSMutableData data];
                for(NSString *key in [post allKeys]){
                    id value = [post objectForKey:key];
                    if (value) {
                        id name = key;
                        if([value isKindOfClass:[NSDictionary class]]){
                            //文件域
                            name = [value objectForKey:@"name"];
                            value = [value objectForKey:@"value"];
                        }
                        if (name && value) {
                            if(name && [value isKindOfClass:[NSData class]]){
                                NSString *temp=[NSString stringWithFormat:@"--%@\r\nContent-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\nContent-Type: application/octet-stream\r\n\r\n", boundary, key, name];
                                [body appendData:[temp dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES]];
                                [body appendData:value];
                            }else{
                                NSString *temp=[NSString stringWithFormat:@"--%@\r\nContent-Disposition: form-data; name=\"%@\"\r\n\r\n%@", boundary, key, value];
                                [body appendData:[temp dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES]];
                            }
                            [body appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
                        }
                    }
                }
                NSString *temp = [NSString stringWithFormat:@"--%@--\r\n",boundary];
                [body appendData:[temp dataUsingEncoding:NSUTF8StringEncoding]];
                //
                [request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary] forHTTPHeaderField:@"Content-Type"];
                [request setValue:[NSString stringWithFormat:@"%llu", (unsigned long long)[body length]] forHTTPHeaderField:@"Content-Length"];
                [request setHTTPBody:body];
            }
            if ([post isKindOfClass:[NSString class]]) {
                NSData *body = [post dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
                [request setValue:[NSString stringWithFormat:@"%llu", (unsigned long long)[body length]] forHTTPHeaderField:@"Content-Length"];
                [request setValue:@"application/x-www-form-urlencoded;" forHTTPHeaderField:@"Content-Type"];
                [request setHTTPBody:body];
            }
            if ([post isKindOfClass:[NSData class]]) {
                [request setValue:@"application/octet-stream;" forHTTPHeaderField:@"Content-Type"];
                [request setValue:[NSString stringWithFormat:@"%llu", (unsigned long long)[post length]] forHTTPHeaderField:@"Content-Length"];
                [request setHTTPBody:post];
            }
            [request setHTTPMethod:@"POST"];
        }else{
            [request setHTTPMethod:@"GET"];
        }
        [request setTimeoutInterval:360];
        return request;
    }
    return nil;
}
@end


//
#pragma mark-
#pragma mark NSString
@implementation NSString (TMLoader)
-(NSString*)md5{
    if ([self length] > 0) {
        const char *cc = [self UTF8String];
        unsigned char result[CC_MD5_DIGEST_LENGTH];
        CC_MD5(cc, (CC_LONG)strlen(cc), result);
        //
        NSMutableString *hash = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH];
        for(uint i = 0; i < CC_MD5_DIGEST_LENGTH; i++){
            [hash appendFormat:@"%02x",result[i]];
        }
        return [hash lowercaseString];
    }
    return self;
}
@end


//
#pragma mark-
#pragma mark UIDevice
NSString *const UIDeviceNetWorkDidChangeNotification = @"UIDeviceNetWorkDidChangeNotification";
@implementation UIDevice(TMLoader)
@dynamic network;
static UIDeviceNetwork network;
static SCNetworkReachabilityRef reachability = NULL;
static void detectNetworkCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info){
    UIDevice *device = (__bridge UIDevice*)info;
    [device netWorkDidChange:flags];
}
+(void)initialize{
    [[UIDevice currentDevice] initReachability];
}
-(void)initReachability{
    if (self == [UIDevice currentDevice]) {
        static dispatch_once_t onceToken = 0;
        dispatch_once(&onceToken, ^{
            reachability = SCNetworkReachabilityCreateWithName(NULL, "0.0.0.0");
            if(reachability) {
                SCNetworkReachabilityContext context = {0, (__bridge  void *)self, NULL, NULL, NULL};
                SCNetworkReachabilitySetCallback(reachability, detectNetworkCallback, &context);
                SCNetworkReachabilityScheduleWithRunLoop(reachability, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
                //抓取状态
                network=UIDeviceNetworkNone;
                SCNetworkReachabilityFlags flags = 0;
                BOOL retrieveFlags = SCNetworkReachabilityGetFlags(reachability, &flags);
                if(retrieveFlags){
                    [self netWorkDidChange:flags];
                }
            }
        });
    }
}
-(UIDeviceNetwork)network{
    return network;
}
-(void)netWorkDidChange:(SCNetworkReachabilityFlags)flags{
    UIDeviceNetwork temp = UIDeviceNetworkNone;
    if((flags & kSCNetworkFlagsReachable) && !(flags & kSCNetworkFlagsConnectionRequired)){
        if((flags & kSCNetworkReachabilityFlagsConnectionRequired) == 0) {
            temp = UIDeviceNetworkWiFi;
        }
        if ((((flags & kSCNetworkReachabilityFlagsConnectionOnDemand ) != 0) || (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0)){
            if ((flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0){
                temp = UIDeviceNetworkWiFi;
            }
        }
        if ((flags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN){
            temp = UIDeviceNetworkWWAN;
        }
    }
    if (temp != network) {
        network = temp;
        [[NSNotificationCenter defaultCenter] postNotificationName:UIDeviceNetWorkDidChangeNotification object:self];
    }
}
@end


//
#pragma mark-
#pragma mark UIImageView
@implementation UIImageView (TMLoader)
static const char *imageViewSrc = "imageView->src";
static const char *imageViewLoader = "imageView->loader";
static const char *imageViewComplete = "imageView->complete";
-(void)load:(NSString*)file base:(NSString*)base complete:(void (^)(id target))complete{
    NSString *src = file;
    if (base) {
        NSString *tmp = [base stringByAppendingPathComponent:file];
        src = [tmp stringByReplacingOccurrencesOfString:@":/" withString:@"://"];
    }else{
        file = nil;
    }
    //
    NSURL *url = [NSURL URLWithString:src];
    if (url) {
        NSURLRequest *request = [NSURLRequest requestWithURL:url];
        if ([request isEqual:self.loader.request] == NO) {
            [self setComplete:complete];
            [self.loader load:request file:file];
        }
    }
}
-(void)load:(NSString*)file base:(NSString*)base{
    [self load:file base:base complete:nil];
}
-(void)openLoader:(TMLoader *)loader{
    [self displayIndicator];
}
-(void)completeLoader:(TMLoader *)loader error:(NSError*)error{
    if (error == nil) {
        UIImage *image = [UIImage imageWithContentsOfFile:loader.filePath];
        if (image) {
            [self setImage:image];
        }
    }else{
        [self setImage:nil];
    }
    if (self.complete) {
        self.complete(self);
        [self setComplete:nil];
    }
    [self hiddenIndicator];
}
-(TMLoader*)loader{
    TMLoader *temp = objc_getAssociatedObject(self, imageViewLoader);
    if (temp == nil) {
        temp = [[TMLoader alloc] initWithDelegate:self cache:true];
        objc_setAssociatedObject(self, imageViewLoader, temp, OBJC_ASSOCIATION_RETAIN);
    }
    return temp;
}
-(void)setComplete:(void (^)(id target))complete{
    objc_setAssociatedObject(self, imageViewComplete, complete, OBJC_ASSOCIATION_COPY);
}
-(void (^)(id target))complete{
    return objc_getAssociatedObject(self, imageViewComplete);
}
-(void)setSrc:(NSString*)src{
    objc_setAssociatedObject(self, imageViewSrc, src, OBJC_ASSOCIATION_COPY);
    [self load:src base:nil];
}
-(NSString*)src{
    return objc_getAssociatedObject(self, imageViewSrc);
}
-(void)displayIndicator{
    UIActivityIndicatorView *v = [self getIndicator];
    if (v == nil) {
        v = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
        [v setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
        [v setFrame:self.bounds];
        [self addSubview:v];
        [v startAnimating];
    }
}
-(void)hiddenIndicator{
    UIActivityIndicatorView *v = [self getIndicator];
    if (v) {
        [v removeFromSuperview];
        [v stopAnimating];
    }
}
-(id)getIndicator{
    for(UIView *v in self.subviews.reverseObjectEnumerator){
        if ([v isKindOfClass:UIActivityIndicatorView.class]) {
            return v;
        }
    }
    return nil;
}
@end
